package com.example.mykotlin.base.flex

import com.example.mykotlin.base.util.AuditLogAspect
import com.example.mykotlin.base.util.Constant
import com.example.mykotlin.base.util.GetBean
import com.example.mykotlin.base.util.SqlFormat
import com.example.mykotlin.base.util.tryOnly
import com.example.mykotlin.base.util.tryReturn
import com.example.mykotlin.mapper.AuditLogMapper
import com.example.mykotlin.model.pojo.AuditLogData
import com.mybatisflex.core.mybatis.TypeHandlerObject
import com.mybatisflex.kotlin.extensions.db.mapper
import java.sql.Statement
import java.text.SimpleDateFormat
import java.time.LocalDate
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter
import java.util.Date
import java.util.regex.Pattern
import org.apache.ibatis.executor.statement.StatementHandler
import org.apache.ibatis.plugin.Interceptor
import org.apache.ibatis.plugin.Intercepts
import org.apache.ibatis.plugin.Invocation
import org.apache.ibatis.plugin.Signature
import org.springframework.jdbc.core.ColumnMapRowMapper
import org.springframework.jdbc.core.RowMapper
import org.springframework.stereotype.Component


@Intercepts(
    Signature(
        type = StatementHandler::class, method = "update", args = [Statement::class]
    )
)
@Component
class MybatisInterceptor : Interceptor {
    companion object {
        private const val TEMP_TABLE_PREFIX = "table"
        private const val TEMP_TABLE_SUFFIX = "tmp"
        private const val DATE_FORMAT = "yyyy-MM-dd HH:mm:ss.SSSSSS"
        private const val PLACEHOLDER = "#"
        private const val INSERT = "insert"
        private const val UPDATE = "update"
        private const val DELETE = "delete"
        private const val AUDIT_LOG_TABLE_NAME = "audit_log"

        private const val PATTERN_REGEX = "'.*?'"
        private val PATTERN = Pattern.compile(PATTERN_REGEX)
        private val PATTERN_TABLE = Pattern.compile("^[A-Za-z]\\w*$")
        private val DTF: DateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSSSSS")


        private fun checkTableName(tableName: String): Boolean {
            val matcher = PATTERN_TABLE.matcher(tableName)
            return matcher.matches()
        }

        // 如果参数是String，则添加单引号， 如果是日期，则转换为时间格式器并加单引号； 对参数是null和不是null的情况作了处理
        private fun getParameterValue(obj: Any?) = when (obj) {
            is String -> "'${SqlFormat.format(obj)}'"
            is TypeHandlerObject -> {
                when (val value = obj.value) {
                    is Date -> {
                        val formatter = SimpleDateFormat(DATE_FORMAT)
                        "'${formatter.format(value)}'"
                    }

                    is LocalDate -> "'${Constant.DF.format(value)}'"
                    is LocalDateTime -> "'${DTF.format(value)}'"
                    else -> value.toString()
                }
            }

            else -> (obj ?: "null").toString()
        }

        private fun getTempTableName(tableName: String) = "${TEMP_TABLE_PREFIX}${tableName}${TEMP_TABLE_SUFFIX}"

        private fun getParam(obj: Any?) = when (obj) {
            is TypeHandlerObject -> {
                when (val value = obj.value) {
                    is Date -> {
                        val formatter = SimpleDateFormat(DATE_FORMAT)
                        formatter.format(value)
                    }

                    is LocalDate -> Constant.DF.format(value)
                    is LocalDateTime -> DTF.format(value)
                    else -> value
                }
            }

            else -> obj
        }
    }


    // 重写拦截方法，拦截数据库操作
    override fun intercept(invocation: Invocation): Any {
        // 获取审计日志数据信息
        val auditLogDataInfo = AuditLogAspect.AUDIT_LOG_DATA_INFO.get()

        // 初始化变量
        var tableFinalChangeData: AuditLogData? = null

        // 初始化变量
        var sqlType = ""
        var tableName = ""
        var primaryKeys: List<String>? = null

        // 初始化变量
        var oldData: MutableList<MutableMap<String, Any?>>? = null
        var newData: MutableList<MutableMap<String, Any?>>? = null
        var mapInsertData: MutableMap<String, MutableMap<String, Any?>>? = null
        var mapOldUpdateData: MutableMap<String, MutableMap<String, Any?>>? = null
        var mapNewUpdateData: MutableMap<String, MutableMap<String, Any?>>? = null
        var mapDeleteData: MutableMap<String, MutableMap<String, Any?>>? = null
        tryOnly {
            // 如果审计日志数据信息为空，直接返回
            if (auditLogDataInfo == null) return@tryOnly
            // 获取目标对象
            val statementHandler = invocation.target as StatementHandler
            // 获取绑定SQL
            val boundSql = statementHandler.boundSql
            // 获取参数对象
            val parameterObject = boundSql.parameterObject
            // sql语句中多个空格都用一个空格代替
            val preparedSql = boundSql.sql.trim().replace("\\s+".toRegex(), " ")
            // var sql = preparedSql
            var params: Array<Any?> = arrayOfNulls(0)
            // 如果参数对象是Map
            if (parameterObject is Map<*, *>) {
                // 获取sql参数
                val field = "\$\$sql_args"
                val sqlArgs = parameterObject[field] as Array<*>
                val length = sqlArgs.size
                // 替换sql参数
                params = Array(length) {
                    getParam(sqlArgs[it])
                }
                // for (arg in sqlArgs) {
                //     sql = sql.replaceFirst("?", getParameterValue(arg));
                // }
            }

            // 替换sql参数
            val placeholderSqlPair = replaceParamToPlaceholder(preparedSql)
            val placeholderSqlArray = placeholderSqlPair.first.replace("`", "").replace("\\W".toRegex(), " $0 ")
                .replace("\\s+".toRegex(), " ").split(" ")

            // 获取操作的表名
            sqlType = placeholderSqlArray[0].lowercase()
            // 获取表名
            tableName = when (sqlType) {
                INSERT -> placeholderSqlArray[2].lowercase()
                UPDATE -> placeholderSqlArray[1].lowercase()
                DELETE -> placeholderSqlArray[2].lowercase()
                else -> return@tryOnly
            }
            // 如果表名不合法或是审计日志表名，直接返回
            if (!checkTableName(tableName) || AUDIT_LOG_TABLE_NAME == tableName) return@tryOnly
            // 获取临时表名
            val tempTableName = getTempTableName(tableName)
            // 获取所有的修改数据
            val finalAllChangeDataList = auditLogDataInfo.finalAllChangeData
            // 遍历所有的修改数据
            for (finalChangeData in finalAllChangeDataList) {
                // 如果表名相等，获取修改数据
                if (tableName == finalChangeData.table) {
                    tableFinalChangeData = finalChangeData
                    break
                }
            }
            // 如果修改数据为空，创建一个新的修改数据
            if (tableFinalChangeData == null) {
                tableFinalChangeData = AuditLogData()
                finalAllChangeDataList.add(tableFinalChangeData!!)
            }
            // 设置表名
            tableFinalChangeData!!.table = tableName
            // 初始化插入数据
            mapInsertData = auditLogDataInfo.insertData.computeIfAbsent(tableName) { mutableMapOf() }
            // 初始化更新数据
            mapOldUpdateData = auditLogDataInfo.oldUpdateData.computeIfAbsent(tableName) { mutableMapOf() }
            // 初始化新更新数据
            mapNewUpdateData = auditLogDataInfo.newUpdateData.computeIfAbsent(tableName) { mutableMapOf() }
            // 初始化删除数据
            mapDeleteData = auditLogDataInfo.deleteData.computeIfAbsent(tableName) { mutableMapOf() }
            // 获取主键
            tryOnly {
                primaryKeys = auditLogDataInfo.primaryKeysInfo.computeIfAbsent(tableName) {
                    mapper<AuditLogMapper>().getPrimaryKey(it)
                }
            }
            // 根据sql类型执行不同的操作
            when (sqlType) {
                INSERT -> tryOnly {
                    // 创建空的临时表，用于记录数据库修改后的数据
                    val tempTableSql = "CREATE TEMPORARY TABLE IF NOT EXISTS `${tempTableName}` like `${tableName}`"
                    tryOnly {
                        GetBean.jdbcTemplate().execute(tempTableSql)
                    }
                    // 更新临时表
                    updateTemporaryTableSql(
                        preparedSql.replaceFirst(tableName, tempTableName), params
                    )
                    // 获取临时表中的数据
                    newData = getAllTemporaryTable(tempTableName)
                    // 清除临时表
                    tryOnly {
                        GetBean.jdbcTemplate().execute("DROP TEMPORARY TABLE IF EXISTS `${tempTableName}`")
                    }

                }

                UPDATE -> tryOnly {
                    // 获取更新的条件和参数
                    val dataWhereSqlAndParams =
                        getDataWhereSqlAndParams(tableName, params, placeholderSqlArray, placeholderSqlPair.second)
                    // 获取旧数据
                    oldData = dataWhereSqlAndParams.third
                    // 把要修改的数据记录到临时表
                    val tempTableSql =
                        "CREATE TEMPORARY TABLE IF NOT EXISTS `${tempTableName}` ${dataWhereSqlAndParams.first}"
                    updateTemporaryTableSql(tempTableSql, dataWhereSqlAndParams.second)
                    // 将sql在记录修改变化后数据临时表中执行，得到修改变化后的数据
                    updateTemporaryTableSql(
                        preparedSql.replaceFirst(tableName, tempTableName), params
                    )
                    newData = getAllTemporaryTable(tempTableName)
                    // 清除临时表
                    tryOnly {
                        GetBean.jdbcTemplate().execute("DROP TEMPORARY TABLE IF EXISTS `${tempTableName}`")
                    }
                }

                DELETE -> tryOnly {
                    // 获取删除的数据
                    oldData = getDataWhereSqlAndParams(
                        tableName, params, placeholderSqlArray, placeholderSqlPair.second
                    ).third
                }

                else -> return@tryOnly
            }
        }
        // 执行原本的数据库交互
        val proceed = invocation.proceed()
        // 根据sql执行的情况来决定是否记录审计日志，存某些特殊业务场景，sql执行失败，数据库数据没有任何变化，但业务事务不回滚，继续执行的情况
        tryOnly {
            // 如果审计日志数据信息为空或表名不合法或是审计日志表名，直接返回
            if (auditLogDataInfo == null || !checkTableName(tableName) || AUDIT_LOG_TABLE_NAME == tableName) return@tryOnly
            // 根据sql类型执行不同的操作
            when (sqlType) {
                INSERT -> {
                    // 获取插入的数据
                    val insertData = indexData(primaryKeys, newData, tableFinalChangeData!!.insert)
                    // 遍历插入的数据
                    for ((key, value) in insertData) {
                        // 先删除，再新增，其实是在进行修改操作，需要将相关数据维护到记录修改的集合中去，之前其他的记录需要移除
                        if (mapDeleteData!!.containsKey(key)) {
                            mapOldUpdateData!![key] = mapDeleteData!![key]!!
                            mapDeleteData!!.remove(key)
                            mapNewUpdateData!![key] = value
                        } else {
                            // 存粹的新增操作，将相关数据维护到记录新增的集合中去即可，需要注意的是新增失败的情况
                            mapInsertData!![key] = value
                        }
                    }
                }

                UPDATE -> {
                    // 获取旧更新的数据
                    val oldUpdateData = indexData(primaryKeys, oldData, tableFinalChangeData!!.oldDataOfUpdate)
                    // 遍历旧更新的数据
                    for ((key, value) in oldUpdateData) {
                        // 先新增，再修改，对于数据库还是在进行新增操作，需要最新的数据维护到记录新增的集合中去
                        // 先修改，再修改，这时不能将数据库中已被修改过的数据维护到记录数据库原始数据的集合（changeDataMap_oldUpdateData）中去
                        // 存粹修改的数据，维护到变化集合中去
                        if (!mapInsertData!!.containsKey(key) && !mapOldUpdateData!!.containsKey(key)) {
                            mapOldUpdateData!![key] = value
                        }
                    }

                    // 获取新更新的数据
                    val newUpdateData = indexData(primaryKeys, newData, tableFinalChangeData!!.newDataOfUpdate)
                    // 遍历新更新的数据
                    for ((key, value) in newUpdateData) {
                        // 先新增，再修改，对于数据库还是在进行新增操作，需要最新的数据维护到记录新增的集合中去
                        // 存粹修改的数据，维护到变化集合中去
                        if (mapInsertData!!.containsKey(key)) {
                            mapInsertData!![key] = value
                        } else {
                            mapNewUpdateData!![key] = value
                        }
                    }
                }

                DELETE -> {
                    // 获取删除的数据
                    val deleteData = indexData(primaryKeys, oldData, tableFinalChangeData!!.delete)
                    // 遍历删除的数据
                    for ((key, value) in deleteData) {
                        // 先新增，再删除，数据库相当于没有进行任何变化，需要将相关数据在所有记录操作的数据集合中移除，其实移除记录新增的集合中的数据即可
                        if (mapInsertData!!.containsKey(key)) {
                            mapInsertData!!.remove(key)
                        } else {
                            mapOldUpdateData!!.remove(key)
                            mapNewUpdateData!!.remove(key)
                            mapDeleteData!![key] = value
                        }
                    }
                }

                else -> return@tryOnly
            }
        }
        // 执行原本的数据库交互
        return proceed
    }


    private fun indexData(
        primaryKeys: List<String>?,
        data: MutableList<MutableMap<String, Any?>>?,
        changeData: MutableList<MutableMap<String, Any?>>
    ): MutableMap<String, MutableMap<String, Any?>> {
        val indexData: MutableMap<String, MutableMap<String, Any?>> = mutableMapOf()
        if (data != null && data.size > 0) {
            if (primaryKeys.isNullOrEmpty()) {
                changeData.addAll(data)
            } else {
                for (dataMap in data) {
                    val key = StringBuilder()
                    for (column in primaryKeys) {
                        key.append(dataMap[column]?.toString() ?: "").append("-")
                    }
                    indexData[key.toString()] = dataMap
                }
            }
        }
        return indexData
    }


    /**
     * 处理未用占位符处理的字符参数
     */
    private fun getDataWhereSqlAndParams(
        tableName: String, params: Array<Any?>, placeholderSqlArray: List<String>, placeholderSqlParams: List<String>
    ): Triple<String, Array<Any?>, MutableList<MutableMap<String, Any?>>> {
        // 将修改变化前数据同步到记录数据库原始数据的临时表中去
        // 首先获取where条件及其参数
        // where条件前的 # 占位符个数
        var skipPlaceholderCount = 0
        // where条件前的 ? 占位符个数
        var skipQuestionPlaceholderCount = 0
        val whereSql = mutableListOf<String>()
        var whereStart = false
        for (sqlElement in placeholderSqlArray) {
            if (PLACEHOLDER == sqlElement && !whereStart) {
                skipPlaceholderCount++
            }
            if ("?" == sqlElement && !whereStart) {
                skipQuestionPlaceholderCount++
            }
            if ("where".equals(sqlElement, true)) {
                whereStart = true
            }
            if (whereStart) {
                whereSql.add(sqlElement)
            }
        }
        // 用于记录将 # 占位符还原的sql信息
        val whereSqlNoHasPlaceholder = mutableListOf<String>()
        var placeholderCount = 0

        // 用于记录 ? 占位符的参数信息
        val whereSqlParams = mutableListOf<Any?>()
        var questionPlaceholderCount = 0
        for (ele in whereSql) {
            if (PLACEHOLDER == ele) {
                whereSqlNoHasPlaceholder.add(placeholderSqlParams[placeholderCount + skipPlaceholderCount])
                placeholderCount++
            } else {
                whereSqlNoHasPlaceholder.add(ele)
            }
            if ("?" == ele) {
                whereSqlParams.add(params[questionPlaceholderCount + skipQuestionPlaceholderCount])
                questionPlaceholderCount++
            }
        }
        val preparedWhereSql = whereSqlNoHasPlaceholder.joinToString(" ").replace("\\s+".toRegex(), " ")

        val whereSqlParamArray = Array(whereSqlParams.size) {
            whereSqlParams[it]
        }

        val oldTableDateSql = "select * from `${tableName}` $preparedWhereSql"
        val oldData = GetBean.jdbcTemplate().query(
            oldTableDateSql, ColumnMapRowMapper() as RowMapper<MutableMap<String, Any?>>, *whereSqlParamArray
        )
        return Triple(oldTableDateSql, whereSqlParamArray, oldData)
    }


    /**
     * 处理未用占位符处理的字符参数
     */
    private fun replaceParamToPlaceholder(sql: String): Pair<String, List<String>> {
        val m = PATTERN.matcher(sql)
        val list = mutableListOf<String>()
        while (m.find()) {
            list.add(m.group())
        }
        var s = sql
        if (list.isNotEmpty()) s = sql.replace(PATTERN_REGEX.toRegex(), PLACEHOLDER)
        return s to list
    }

    /**
     * 执行要使数据变化的sql
     */
    private fun updateTemporaryTableSql(
        sql: String, params: Array<Any?>
    ) = tryReturn(0) {
        GetBean.jdbcTemplate().update(sql, *params)
    }


    /**
     * 获取表数据(临时表中最终的数据)
     */
    private fun getAllTemporaryTable(tempTableName: String): MutableList<MutableMap<String, Any?>>? = tryReturn {
        GetBean.jdbcTemplate().query(
            "select * from $tempTableName", ColumnMapRowMapper() as RowMapper<MutableMap<String, Any?>>
        )
    }

}